#pragma once

#include <algorithm/analytical_model/algorithm/cuda/Utils.h>
#include <algorithm/analytical_model/algorithm/cuda/inter_op/Data.h>

namespace solar::cuda
{
    __host__ __device__ inline constexpr auto
    setThreadBlocks(dim3& n_blocks, int n_threads, size_t size, bool thread_fixed) -> bool
    {
        if (size > inter_op::kMaxAllThreads)
        {
            printf("There are too many threads to cope with. Please use less threads.\n");
            return false;
        }
        // printf("size=%d\n",size);
        int block_last_dim = (size + n_threads - 1) / n_threads;
        // printf("block_lastDim: %d\n" ,block_lastDim);

        if (block_last_dim < inter_op::kMaxBlockSingleDim)
        {
            n_blocks.x = block_last_dim;
            n_blocks.y = 1;
            n_blocks.z = 1;
            // printf("1nBlocks: ( %d, %d, %d )\n", nBlocks.x, nBlocks.y, nBlocks.z);
            return true;
        }

        block_last_dim =
            (block_last_dim + inter_op::kMaxBlockSingleDim - 1) / inter_op::kMaxBlockSingleDim;

        if (block_last_dim < inter_op::kMaxBlockSingleDim)
        {
            n_blocks.x = inter_op::kMaxBlockSingleDim;
            n_blocks.y = block_last_dim;
            n_blocks.z = 1;
            // printf("2nBlocks: ( %d, %d, %d )\n", nBlocks.x, nBlocks.y, nBlocks.z);
            return true;
        }

        n_blocks.x = inter_op::kMaxBlockSingleDim;
        n_blocks.y = inter_op::kMaxBlockSingleDim;
        n_blocks.z =
            (block_last_dim + inter_op::kMaxBlockSingleDim - 1) / inter_op::kMaxBlockSingleDim;
        // printf("3nBlocks: ( %d, %d, %d )\n", nBlocks.x, nBlocks.y, nBlocks.z);
        return true;
    }

    __host__ __device__ inline constexpr auto setThreadBlocks(dim3& nBlocks, int& nThreads,
                                                              const size_t& size) -> bool
    {
        nThreads = (inter_op::kMaxThreads <= size) ? inter_op::kMaxThreads : size;
        return setThreadBlocks(nBlocks, nThreads, size, true);
    }

    __host__ __device__ inline constexpr auto getThreadID() -> unsigned long long int
    {
#if defined(__CUDA_ARCH__)
        // unique block index inside a 3D block grid
        const unsigned long long int block_id = blockIdx.x                            // 1D
                                                + blockIdx.y * gridDim.x              // 2D
                                                + gridDim.x * gridDim.y * blockIdx.z; // 3D
        // global unique thread index, block dimension uses only x-coordinate
        const unsigned long long int thread_id = block_id * blockDim.x + threadIdx.x;
        // printf("blockid=%lld  threadid=%lld\n",blockId,threadId);
        return thread_id;
#else
        return 0;
#endif
    }

    // Unroll the index and address
    __host__ __device__ inline constexpr auto unrollIndex(const int3& index,
                                                          const int3& matrix_size) -> int
    {
        return index.x * matrix_size.y * matrix_size.z + index.y * matrix_size.z + index.z;
    }

    // Unroll the index and address
    __host__ __device__ inline constexpr auto unrollIndex(const int2& index,
                                                          const int2& matrix_size) -> int
    {
        return index.x * matrix_size.y + index.y;
    }
} // namespace solar::cuda
